use crate::error::AppError;
use crate::models::{Claims, User};
use axum::{async_trait, extract::FromRequestParts, http::request::Parts};
use jsonwebtoken::{decode, DecodingKey, Validation};

#[derive(Clone)]
pub struct AuthenticatedUser(pub User);

/// DVC-specific authenticated user that supports both JWT and Basic Auth
#[derive(Clone)]
pub struct DvcAuthenticatedUser(pub User);

#[async_trait]
impl<S> FromRequestParts<S> for AuthenticatedUser
where
    S: Send + Sync,
{
    type Rejection = AppError;

    async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
        // Extract and validate JWT token
        let auth_header = parts
            .headers
            .get("authorization")
            .ok_or_else(|| AppError::Auth("Missing authorization header".to_string()))?;

        let auth_str = auth_header
            .to_str()
            .map_err(|_| AppError::Auth("Invalid authorization header format".to_string()))?;

        if !auth_str.starts_with("Bearer ") {
            return Err(AppError::Auth(
                "Invalid authorization header format".to_string(),
            ));
        }

        let token = &auth_str[7..]; // Remove "Bearer " prefix

        // Get JWT secret from environment
        let jwt_secret = std::env::var("JWT_SECRET")
            .unwrap_or_else(|_| "your-secret-key-here-change-in-production".to_string());

        let token_data = decode::<Claims>(
            token,
            &DecodingKey::from_secret(jwt_secret.as_ref()),
            &Validation::default(),
        )
        .map_err(|_| AppError::Auth("Invalid token".to_string()))?;

        // We need to get the database connection from the state, but FromRequestParts doesn't give us access to AppState
        // For now, we'll create a placeholder user and let the handlers fetch the full user data
        let created_at = chrono::DateTime::from_timestamp(token_data.claims.iat as i64, 0)
            .unwrap_or_else(|| chrono::Utc::now());

        let user = User {
            id: 0, // This will be resolved by the handlers using email lookup
            email: token_data.claims.sub.clone(),
            hashed_password: "".to_string(),
            avatar_url: None,
            is_admin: false, // Will be updated by handlers
            created_at,
            updated_at: created_at,
        };

        Ok(AuthenticatedUser(user))
    }
}

#[async_trait]
impl<S> FromRequestParts<S> for Claims
where
    S: Send + Sync,
{
    type Rejection = AppError;

    async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
        let auth_header = parts
            .headers
            .get("authorization")
            .ok_or_else(|| AppError::Auth("Missing authorization header".to_string()))?;

        let auth_str = auth_header
            .to_str()
            .map_err(|_| AppError::Auth("Invalid authorization header format".to_string()))?;

        if !auth_str.starts_with("Bearer ") {
            return Err(AppError::Auth(
                "Invalid authorization header format".to_string(),
            ));
        }

        let token = &auth_str[7..]; // Remove "Bearer " prefix

        // Get JWT secret from state or environment
        let jwt_secret = std::env::var("JWT_SECRET")
            .unwrap_or_else(|_| "your-secret-key-here-change-in-production".to_string());

        let token_data = decode::<Claims>(
            token,
            &DecodingKey::from_secret(jwt_secret.as_ref()),
            &Validation::default(),
        )
        .map_err(|_| AppError::Auth("Invalid token".to_string()))?;

        Ok(token_data.claims)
    }
}

#[async_trait]
impl<S> FromRequestParts<S> for User
where
    S: Send + Sync,
{
    type Rejection = AppError;

    async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
        // Get the claims from the request
        let claims = Claims::from_request_parts(parts, _state).await?;

        // For now, we'll create a minimal user from the claims
        // In a real implementation, you might want to fetch the full user from the database
        let created_at = chrono::DateTime::from_timestamp(claims.iat as i64, 0)
            .unwrap_or_else(|| chrono::Utc::now());

        let user = User {
            id: 0, // We don't have the user ID in the claims, so we'll need to implement database lookup
            email: claims.sub,
            hashed_password: "".to_string(),
            avatar_url: None,
            is_admin: false,
            created_at,
            updated_at: created_at,
        };

        Ok(user)
    }
}

#[async_trait]
impl<S> FromRequestParts<S> for DvcAuthenticatedUser
where
    S: Send + Sync,
{
    type Rejection = AppError;

    async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
        // Extract authorization header
        let auth_header = parts
            .headers
            .get("authorization")
            .ok_or_else(|| AppError::Auth("Missing authorization header".to_string()))?;

        let auth_str = auth_header
            .to_str()
            .map_err(|_| AppError::Auth("Invalid authorization header format".to_string()))?;

        // Try Basic Auth first (for DVC compatibility)
        if auth_str.starts_with("Basic ") {
            return handle_dvc_basic_auth(auth_str).await;
        }

        // Fallback to Bearer token (JWT)
        if auth_str.starts_with("Bearer ") {
            return handle_jwt_auth(auth_str).await;
        }

        Err(AppError::Auth(
            "Invalid authorization header format. Expected 'Basic ...' or 'Bearer ...'".to_string(),
        ))
    }
}

/// Handle Basic Auth for DVC routes
async fn handle_dvc_basic_auth(auth_str: &str) -> Result<DvcAuthenticatedUser, AppError> {
    // We need to get the database connection from the state, but FromRequestParts doesn't give us access to AppState
    // For now, we'll create a placeholder user and let the handlers fetch the full user data
    // In a real implementation, you might want to use a different approach
    
    // Extract credentials for validation
    let encoded_credentials = auth_str
        .strip_prefix("Basic ")
        .ok_or_else(|| AppError::Auth("Invalid Basic Auth format".to_string()))?;

    // Decode to validate format
    use base64::{Engine as _, engine::general_purpose};
    let decoded_credentials = general_purpose::STANDARD
        .decode(encoded_credentials)
        .map_err(|_| AppError::Auth("Invalid base64 encoding".to_string()))?;

    let credentials_str = String::from_utf8(decoded_credentials)
        .map_err(|_| AppError::Auth("Invalid UTF-8 encoding".to_string()))?;

    let (email, _password) = credentials_str
        .split_once(':')
        .ok_or_else(|| AppError::Auth("Invalid credentials format".to_string()))?;

    // Create placeholder user - actual validation will happen in handlers
    let created_at = chrono::DateTime::from_timestamp(0, 0)
        .unwrap_or_else(|| chrono::Utc::now());

    let user = User {
        id: 0, // Will be resolved by handlers
        email: email.to_string(),
        hashed_password: "".to_string(),
        avatar_url: None,
        is_admin: false, // Will be updated by handlers
        created_at,
        updated_at: created_at,
    };

    Ok(DvcAuthenticatedUser(user))
}

/// Handle JWT Bearer token authentication
async fn handle_jwt_auth(auth_str: &str) -> Result<DvcAuthenticatedUser, AppError> {
    let token = &auth_str[7..]; // Remove "Bearer " prefix

    // Get JWT secret from environment
    let jwt_secret = std::env::var("JWT_SECRET")
        .unwrap_or_else(|_| "your-secret-key-here-change-in-production".to_string());

    let token_data = decode::<Claims>(
        token,
        &DecodingKey::from_secret(jwt_secret.as_ref()),
        &Validation::default(),
    )
    .map_err(|_| AppError::Auth("Invalid token".to_string()))?;

    // Create placeholder user - actual validation will happen in handlers
    let created_at = chrono::DateTime::from_timestamp(token_data.claims.iat as i64, 0)
        .unwrap_or_else(|| chrono::Utc::now());

    let user = User {
        id: 0, // Will be resolved by handlers
        email: token_data.claims.sub.clone(),
        hashed_password: "".to_string(),
        avatar_url: None,
        is_admin: false, // Will be updated by handlers
        created_at,
        updated_at: created_at,
    };

    Ok(DvcAuthenticatedUser(user))
}
